﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace Btstack
{
    public partial class Bluetooth
    {
        private enum ControlCmd
        {
            PowerOn,
            PowerOff,
            StartScan,
            StopScan,
            Connect,
            DisConnect,
            ReadRssi
        }

        private struct ControlData
        {
            public ControlData(ControlCmd cmd)
            {
                this.cmd = cmd;
                Params = new Dictionary<string, object>();
            }
            public readonly ControlCmd cmd;
            public readonly Dictionary<string, object> Params;
        }

        [DllImport("kernel32.dll")]
        private static extern bool SetEvent(IntPtr hEvent);

        [DllImport("kernel32.dll")]
        private static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, IntPtr lpName);

        public event Action OnWorking;
        public event Action OnPowerOnFailed;

        // EventType, addr, AddrType, name, rssi
        public event Action<AdvType, byte[], BdAddrType, string, sbyte> OnAdvertisingReport;

        // status, handle, role, interval, latency
        public event Action<byte, ushort, HciRole, float, ushort> OnConnected;

        public event Action OnDiscoverServiceComplete;

        // handle, status, reason
        public event Action<ushort, byte, byte> OnDisconnected;

        public event Action<ushort, sbyte> OnRssiMeasurementReport;

        private readonly IntPtr HciEventCallbackRegistration;
        private readonly List<ushort> Connections = new List<ushort>();
        public byte[] LocalBdAddr { get; private set; }
        private readonly IntPtr ControlDataSource;
        private readonly BlockingCollection<ControlData> ControlDatas = new BlockingCollection<ControlData>();
        private readonly IntPtr ControlHandle;
        private readonly BtstackPacketHandlerType PacketHandlerPtr;
        private readonly BtstackDataSourceCallbackType HandleControlPtr;

        public Bluetooth()
        {
            HciDumpEnableLogLevel(HCI_DUMP_LOG_LEVEL_DEBUG, false);
            HciDumpEnableLogLevel(HCI_DUMP_LOG_LEVEL_INFO, false);
            HciDumpEnableLogLevel(HCI_DUMP_LOG_LEVEL_ERROR, false);

            BtstackMemoryInit();

            BtstackRunLoopInit(BtstackRunLoopWindowsGetInstance());

            HciInit(HciTransportUsbInstance(), IntPtr.Zero);

            L2capInit();

            LeDeviceDbInit();

            SmInit();
            SmSetIoCapabilities(IoCapability.NoInputNoOutput);

            GattClientInit();

            AttPacketHandlerPtr = AttPacketHandler;
            AttServerRegisterPacketHandler(AttPacketHandlerPtr);

            PacketHandlerPtr = PacketHandler;
            HciEventCallbackRegistration = HciAddEventHandler();

            ControlDataSource = Marshal.AllocHGlobal(Marshal.SizeOf<BtstackDataSourceType>());
            var ds = new BtstackDataSourceType();
            ControlHandle = CreateEvent(IntPtr.Zero, false, false, IntPtr.Zero);
            ds.source.handle = ControlHandle;
            Marshal.StructureToPtr(ds, ControlDataSource, true);

            BtstackRunLoopEnableDataSourceCallbacks(ControlDataSource, BtstackDataSourceEventType.Read);
            HandleControlPtr = HandleControl;
            BtstackRunLoopSetDataSourceHandler(ControlDataSource, HandleControlPtr);
            BtstackRunLoopAddDataSource(ControlDataSource);

            HandleGattClientEventPtr = HandleGattClientEvent;
        }

        ~Bluetooth()
        {
            Marshal.FreeHGlobal(HciEventCallbackRegistration);
            Marshal.FreeHGlobal(ControlDataSource);
            Marshal.FreeHGlobal(ControlHandle);
        }

        private IntPtr HciAddEventHandler()
        {
            var HciEventCallbackRegistration = new BtstackPacketCallbackRegistrationType
            {
                callback = PacketHandlerPtr
            };

            IntPtr p = Marshal.AllocHGlobal(Marshal.SizeOf<BtstackPacketCallbackRegistrationType>());

            Marshal.StructureToPtr(HciEventCallbackRegistration, p, true);

            HciAddEventHandler(p);

            return p;
        }

        private void HandleControl(IntPtr ds, BtstackDataSourceEventType e)
        {
            while (true)
            {
                if (!ControlDatas.TryTake(out ControlData  c))
                    break;

                switch (c.cmd)
                {
                    case ControlCmd.PowerOff:
                        HciPowerControl(HciPowerMode.Off);
                        break;

                    case ControlCmd.PowerOn:
                        HciPowerControl(HciPowerMode.On);
                        break;

                    case ControlCmd.StartScan:
                        var Scantype = (ScanType)c.Params["type"];
                        var interval = (ushort)c.Params["interval"];
                        var window = (ushort)c.Params["window"];

                        GapSetScanParameters(Scantype, interval, window);
                        GapStartScan();

                        break;

                    case ControlCmd.StopScan:
                        GapStopScan();
                        break;

                    case ControlCmd.DisConnect:
                        {
                            var handle = (int)c.Params["handle"];
                            if (handle < 0)
                                HciDisconnectAll();
                            else
                                GapDisconnect((ushort)handle);
                        }
                        break;

                    case ControlCmd.ReadRssi:
                        {
                            var handle = (ushort)c.Params["handle"];
                            GapReadRssi(handle);
                        }
                        break;

                    case ControlCmd.Connect:
                        var AddrType = (BdAddrType)c.Params["type"];
                        var addr = c.Params["addr"];
                        if (addr is string)
                            GapConnect(addr.ToString(), AddrType);
                        else
                            GapConnect((byte[])addr, AddrType);
                        break;

                    default:
                        break;
                }
            }
        }

        private void HandleConnection(byte[] packet)
        {
            switch (HciEventLeMetaGetSubeventCode(packet))
            {
                case HCI_SUBEVENT_LE_CONNECTION_COMPLETE:
                    var status = HciSubeventLeConnectionCompleteGetStatus(packet);
                    var handle = HciSubeventLeConnectionCompleteGetConnectionHandle(packet);
                    HciRole role = HciSubeventLeConnectionCompleteGetRole(packet); // Relative to myself
                    var interval = HciSubeventLeConnectionCompleteGetConnInterval(packet);
                    var latency = HciSubeventLeConnectionCompleteGetConnLatency(packet);

                    OnConnected?.Invoke(status, handle, role, interval, latency);

                    if (role == HciRole.Slave)
                    {
                        Connections.Add(handle);
                        GapRequestConnectionParameterUpdate(handle, 12, 12, 0, 0x0048); // request min con interval 15 ms for iOS 11+
                    }
                    else
                    {
                        Connections.Clear();
                        Connections.Add(handle);
                        GattClientDiscoverPrimaryServices(handle);
                    }
                    break;
                default:
                    break;
            }
        }

        private void PacketHandler(byte pt, ushort channel, IntPtr PacketPtr, ushort size)
        {
            if (pt != HCI_EVENT_PACKET)
                return;

            byte[] packet = new byte[size];
            Marshal.Copy(PacketPtr, packet, 0, size);

            ushort handle;
            sbyte rssi;

            switch (HciEventPacketGetType(packet))
            {
                case BTSTACK_EVENT_POWERON_FAILED:
                    OnPowerOnFailed?.Invoke();
                    break;

                case BTSTACK_EVENT_STATE:
                    if (BtstackEventStateGetState(packet) != HciState.Working)
                        return;

                    LocalBdAddr = GapLocalBdAddr();
                    OnWorking?.Invoke();

                    break;

                case GAP_EVENT_ADVERTISING_REPORT:
                    var type = GapEventAdvertisingReportGetAdvertisingEventType(packet);
                    var AddrType = GapEeventAdvertisingReportGetAddressType(packet);
                    var addr = GapEventAdvertisingReportGetAddress(packet);
                    var name = GapAdvertisementReportName(packet);
                    rssi = GapEventAdvertisingReportGetRssi(packet);

                    OnAdvertisingReport?.Invoke(type, addr, AddrType, name, rssi);

                    break;

                case HCI_EVENT_LE_META:
                    HandleConnection(packet);
                    break;

                case HCI_EVENT_DISCONNECTION_COMPLETE:
                    handle = HciEventDisconnectionCompleteGetConnectionHandle(packet);
                    byte status = HciEventDisconnectionCompleteGetStatus(packet);
                    byte reason = HciEventDisconnectionCompleteGetReason(packet);

                    Connections.Remove(handle);

                    StopListeningForCharacteristicValueUpdates(IntPtr.Zero);

                    OnDisconnected?.Invoke(handle, status, reason);
                    break;

                case GAP_EVENT_RSSI_MEASUREMENT:
                    handle = GapEventRssiMeasurementGetConHandle(packet);
                    rssi = GapEventRssiMeasurementGetRssi(packet);
                    OnRssiMeasurementReport?.Invoke(handle, rssi);
                    break;

                default:
                    break;
            }
        }

        public void StartRunLoop()
        {
            BtstackRunLoopExecute();
        }

        public void PowerOn()
        {
            ControlDatas.Add(new ControlData(ControlCmd.PowerOn));
            SetEvent(ControlHandle);
        }

        public void PowerOff()
        {
            ControlDatas.Add(new ControlData(ControlCmd.PowerOff));
            SetEvent(ControlHandle);
        }

        public HciState State()
        {
            return HciGetState();
        }

        // -1 for all
        public void Disconnect(int handle = -1)
        {
            var c = new ControlData(ControlCmd.DisConnect);
            c.Params["handle"] = handle;
            ControlDatas.Add(c);
            SetEvent(ControlHandle);
        }

        public void ReadRssi(ushort handle)
        {
            var c = new ControlData(ControlCmd.ReadRssi);
            c.Params["handle"] = handle;
            ControlDatas.Add(c);
            SetEvent(ControlHandle);
        }
    }
}
